Welcome back.
In a previous lecture in the Lua basics section, we learned about error messages and handling errors
using the Pcall function.
Now that we're diving into the Roblox API, it's time to continue expanding our error handling arsenal
by taking a look at some of the tools studio offers for debugging errors, and also looking at the documentation
to spot any functions we should wrap in a PE call.
Now.
Earlier we took a look at breakpoints, which are those nifty little things you can add in your scripts,
which will halt the execution of the script, and it will allow you to go line by line, taking a look
at how your code is executing.
But you probably didn't know that there are even more settings that we can access with breakpoints.
So with this example code I have here, I'm going to add a breakpoint at the calling of the main function.
And then we can right click our breakpoint to access a new window for editing some settings in the breakpoint.
In fact, there are some settings where we can have the breakpoint be reached or used.
When only a certain condition is met, we can have it print a message out into the console, and we
can even continue the execution of this script without halting the script and going line by line.
Instead, if we want a breakpoint to act purely as like a log message, then we can hit continue execution,
and then we can print a log message in here.
And then the format of this log message has to be the same as if you were, let's say, putting some
arguments in a print statement.
So for example it says quotation the value of n colon quotation.
And then you do comma n.
If you want to print out a variable in your log message.
We also have an option if we want to delete the breakpoint once we hit it.
So if you only want to reach a breakpoint once when you're debugging, you can enable that setting and
then it will automatically remove the breakpoint for you.
And then we also have some different contexts in here.
So for example, if your breakpoint is in a module script that could get required by the client or the
server, and you only want to debug an error that's occurring on the server, well, you can get rid
of the context on the client.
And then this other context down here called edit.
I believe this is only for plugins, so you don't need to worry about that.
But the two you want to focus on are going to be the client and the server.
But as you can see, there's just a whole bunch of different options here with breakpoints.
So let's go ahead and actually take an example of utilizing some of these settings.
So what I'm going to do is I'm actually going to create a variable before our breakpoint.
Let's just call it uh example var and we're going to set it equal to ten.
Now let's say we only want to reach this breakpoint when example var is equal to 20.
So what we can do is we can type out the variable name which is going to be example var.
And since this has to be an expression we're going to do equal equal 20.
So if this evaluates to true we're going to reach our breakpoint.
And then when we also reach our breakpoint let's go ahead and print a message like the value of example
var is.
We need to close that with quotations since this is a string.
And then we're going to put a comma and then do example var.
So this is the exact same way as if you were typing in arguments for a print statement.
And then we're going to continue execution and not halt the code.
So let's go ahead and save that.
You're going to see that the symbol has changed for our breakpoint, because now it is a log point because
we're printing out a log message.
But let's go ahead and run this code.
And what you're going to see is nothing happened.
We got the print statements that were inside of our function.
For example, it prints before division and then it prints the result after the division.
But you see, it didn't print the message from our log point.
And that's because this condition evaluated to false.
Now if we went and set example of are equal to 20 and then we run it.
As you can see the value of example var is 20.
So our break point was reached.
It printed in the console.
And that's because the condition inside of our break point evaluated to true.
And then it also didn't halt the execution of our script because we have continue execution enabled
If we decide to disable that.
And then actually let me make sure it's disabled, okay, it's disabled.
And then if we were to run the code again, as you can see, we get put inside of the debugging menu.
And now we have to go through line by line inside of our code.
So you should remember the three different options we have.
Step into step over and step out.
So let's go ahead and step into our main function.
Now inside of our main function let's continue stepping.
And then we should be able to print before division they're printed.
And then we're going to step into our divide function.
We're going to perform the division.
And then we print our result.
We reach the end of the scope for this function.
And we go back to where the main function was called.
And we're done.
That's the end of this particular script.
Now another useful thing with breakpoints is that we can add breakpoints while we're running the game,
and that allows us to continue.
Let's say we found a bug somewhere, and we want to figure out what exactly is causing the bug.
So let's say this bug can repeat itself, and then we need to add a new breakpoint somewhere.
Well, while the game is running we can add a breakpoint, and if we want it to print something in the
console, we can totally do that.
We don't have to stop play testing and then go in here and add another print statement in the code,
and then go back and play test again.
We can do it in real time.
So as an example, what we're going to do is we're going to run this exact same code again.
And right now we're currently at the calling of the main function.
Now what I'm going to do is I'm going to add another breakpoint here.
And let's go ahead and edit this breakpoint.
And what we're going to do is we're just going to simply print out what the value of x and y is.
So we can just do x comma y.
And then we're going to have this uh continue execution I'm going to save that.
So we just added a breakpoint while we're in the debugging menu.
And then now let's just step into our code.
So now we create x and y we go to the print statement.
And as you can see it also printed out the value of x and y while we were in the middle of debugging.
So that's another useful thing with breakpoints.
We can just add them while we are in the process of debugging our code.
For example, we can have this one print.
Let's have this one print the results and we'll continue execution and we'll step over to get the result.
There we go.
It printed two, which was the result.
And then we're going to call the print function again which printed two once more.
And then we'll step out.
And then our debugging process is over.
But as you can see that's really cool.
We can add breakpoints in the middle of debugging our code.
We can't do that with regular print statements.
Now we have even more debugging tools at our disposal.
If we head to the view tab, we can open up another window called watch.
And this one's a very interesting window because what it allows us to do is it allows us to watch the
values of variables depending on a particular scope.
And we can filter the scope out too.
So if we hit this button right here that says all scopes, we can actually toggle what scope we want
to look at variables.
If it's going to be a global variable, an up value or a local variable.
And then it also gives you descriptions of each.
So a local variable is accessible only within the block of code where it's declared.
And upvalue is a local variable that an inner function or a loop uses.
So an upvalue would basically be, for example, if we had a variable up here called hello, that's
equal to ten.
If the current thread of execution is inside of our main function, we can access hello.
But hello is not a global variable because we declared it to be local.
But it's also not local to our main function.
So therefore it's considered what we would call an Upvalue.
So what I'm going to do is I'm going to delete these log points, and we're going to go back and run
our code, and we're going to pay attention to this watch menu.
As you can see right now we're in the debugging process.
And we can see a whole bunch of different variables here.
Here are some global variables which we don't really care about right now.
So let's actually hide all of the global variables.
So right now we have access to three different variables in the current scope we're at.
So the current thread of execution is at this line.
In this scope.
And within the scope we can see three different variables.
We can see our main function.
We can see our example variable and we can see our divide function.
And it tells us what the values are as well.
So these two are functions.
And our example variable has a value of 20.
As we continue to step into the code you're going to see these values change.
Right now we can only see divide because we're in the scope of the function.
We're not able to see example var out here.
We can only see divide right now.
But once we create those two variables x and y now they appear inside of the variable section and they're
both local.
And you can also see that it declares that the divide function is an up value, which makes sense because
divide is not a function that's a local to this scope, but it's also not a global function, so it's
an up value.
Now, another useful feature with this watch menu is we can go to this other tab called My Watches.
And it allows you to enter in expressions.
An expression is basically something like a Boolean expression or an arithmetic expression.
It's just a result or a series of executions that produce some kind of result.
So for example, an arithmetic expression would be x plus y and we can hit enter and look at that.
It knows that it produces the value of 15.
If we look at the values of x and y x is ten, y is 15.
And therefore the result of this expression x plus y is 15.
And we can do this while we're in the middle of debugging our code.
We could do x minus y.
We could do x times y.
And as you can see it's going to show us each one of those values based on this expression and what
is currently stored in these variables.
Now let's say we want to see the expression of result plus x or something like that.
We could do result plus X, but right now you're going to see that it says value is not found.
And that's because we're currently on line number nine.
And we haven't created the result variable yet.
So let's continue stepping into our code.
Let's perform the division.
We'll create our result variable.
And there you go.
Now it has a value of result plus x.
Because if we look at the variables tab we can see our result variable.
And that is holding the value of two.
So two plus ten is equal to 12.
Another cool feature while we're in the middle of debugging is that if you hover your mouse over a particular
variable like result and you keep it there, you're going to see a little window pop up and it's going
to tell us what the value is stored in that variable.
So as you can see result is two.
How about x.
Let's take a look at x x is ten.
How about y y is five.
Wow isn't that pretty nifty.
These are all features that print statements can't give you.
Sure, print statements are fast and easy to implement, but breakpoints give you so many more features
that make debugging a breeze.
Whether it's a logical error, whether it is a runtime error, using breakpoints can make the debugging
process very fast, especially in conjunction with this watch menu.
Let me slap in a few more expressions inside of the My Watch's menu, for example, is result greater
than x?
It's false.
Of course.
Result is not greater than x.
Is x greater than result.
Ah, that's true because x is ten, result is two.
How about let's do a little bit more of a complex boolean expression.
Is x greater than um let's say y and let's say result is greater than y.
Oh that's false because x is ten, y is five, result is 12 and y is five.
So this evaluated to false because not both conditions were true.
So like I said there's a lot of different possibilities you can do with this.
My watches section in the watch menu.
Now one more debugging menu I'd like to show you is going to be something called the Script Analysis
window, which you might have seen open down here.
And basically what this script analysis window does is it'll show you any compile time errors or any
warnings inside of your scripts, whether that's type annotation warnings, whether you made a mistake
in your code somewhere, it's going to spit out those warnings in here and it'll tell you what script
that warning is coming from.
So as an example, let's say we made a really silly mistake with our main function and we forgot an
end statement to close out the scope of the main function.
Well, as you can see in the script analysis window, we have an error expected end to close function
at line six.
And instead we got end of file.
So we can click that.
It's going to highlight the error.
As you can see the underline is right here.
And it says the exact same error message.
But it says we need to close a function at line six.
So if we go up to line six here's our function and we can realize, oh silly me, I forgot to put an
end statement here to close out the main function.
And as you can see, the error is now gone from the script analysis window.
I personally use the script analysis window while I am scripting, just in case any errors appear in
other scripts while I'm modifying.
Let's say a module that is used by other scripts when I am modifying that module.
If any other script that's using it.
Um, let's say an error occurs because I made a stupid mistake in the module.
All of those errors will pop up inside of the script analysis window.
So I can go ahead and take a look at what the issue is.
Here I have some example code that we're going to practice using breakpoints with.
This code produces an intermittent error and that occurs when this random number generator produces
a value of zero.
And that's because we are not able to divide by zero when we divide a number by zero.
This divide function is going to return a number called Nan, which is basically an invalid number.
It's a number, but we shouldn't be using it in our code.
And the issue with this is that if this returns Nan, when we try to set an index in our table that
we're creating right here, and we try to set it with the index of Nan, we're going to run into a problem.
And this only happens intermittently if the random number generator generates a value of zero.
So what we're going to do is we're going to run this code and we're going to see how long it's going
to take for us to reach an error.
So we're going to run it.
So far we're having no issues.
Oh and there we go.
We got an error table.
Index is Nan.
but our code is still continuing to run.
And as you can see, this error is only happening when that random number generator produces a value
of zero.
So what we're going to do right now is we're going to pause the game because we need to start debugging.
So as you can see the viewport is paused for debugging.
We're going to hop into our script where the error is occurring.
So we can click this.
It's going to show us the line that the error is occurring.
And we need to figure out why this is happening.
So because we're in the debugging process, we are able to add in breakpoints right away to see what
the heck is going on.
So since we know the error is happening right here, let's add a breakpoint above just to see what the
value of x and y is going to be and what the value of result is going to be.
So let's go ahead and resume.
We're going to hit that break point.
And now we are at this line of before division.
So let's go ahead and step into that.
Let's see what we print.
We print it out before Division.
Let's continue stepping into the code.
We're going to get a slash B and we're going to get a result.
Now let's go ahead and open up our watch menu.
And let's go ahead and take a look at what the current values are.
So x is currently one, y is currently negative one.
And our result is a value of negative one.
Let's continue stepping into the code.
Prints my table with a key of negative one.
Now typically you don't want to have tables with negative keys but that doesn't produce an error.
So it's fine.
And let's just continue stepping through the code.
Actually let's step into okay we're back at the breakpoint again.
Let's see what the value of x and y is this time okay.
They're both negative one.
So we'll just continue stepping into the code see what result is.
Result is one okay.
No issues.
We're going to continue going through the code.
All right this time.
Look at that x is negative one y is zero.
So at this point if you're a good developer you should already recognize we're going to run into an
issue.
But let's continue stepping into the code.
Let's perform our division and let's get our result variable.
And result is actually negative infinity.
So inside of our table, if we set that I don't think we're going to actually run into an issue okay.
No we didn't run into an issue.
So let's just keep going through the code.
This time x is zero y is negative one.
Let's keep going.
Okay no issues so far.
And as you can see x and y are now both zero.
So let's see what result variable or the value we get in result.
And look at that.
Finally we have reached the crux of the problem.
We have gotten Nan which is a problem because if we try to set that in the table, boom, we get our
error.
So now we have a pretty good idea of why this error is occurring.
Let's go ahead and try to fix it in our code.
Now one way we could fix it is we could basically say, hey, you know, if y is zero, let's just not
do anything, but let's add in some error handling inside of the divide function.
Or actually let's throw an error if we try to divide by zero.
So if b is equal to zero, then we're just going to throw an error that we cannot divide by zero.
And then we need to handle this error.
So this is where Paul is going to come into play.
So let's go ahead and do pcall.
And we're going to call the divide function and pass x and y.
And we need to get that result back.
So let's return it.
And then this p call is going to give us a boolean of whether or not we were successful in executing
the code as well as our result.
So now we can go ahead and get rid of this.
And now what we can do before we set result inside of our table, we need to check if we were successful,
if we were not successful.
So let's do not.
Then we're just going to return and do nothing.
And maybe we can print out a message of the error.
Maybe we'll put a warning here and we'll print out what the result was from this call, which should
be the error message if we were not successful.
And then let's go ahead and re-add our breakpoint right here and let's run the code again.
So now we're back in the debugging process.
Let's step into our code Okay, pretty lucky we got both zero and zero.
So this should produce an error.
So if we step into this we do that we're going to get our error.
Boom error happened.
As you can see success is set to false.
And the result is what result is probably a string right.
Yeah.
There's our string value cannot divide by zero.
So if we step into this we're obviously not successful.
We're going to get a warning printed out in the console.
Cannot divide by zero.
And then we just return and we move on to the next iteration of the loop.
And just like that, we have fixed our intermittent bug by handling it with a p call.
All right.
So the last thing we're going to do is we're going to hop into the Roblox documentation.
And we're going to look around to see if a function in some kind of service needs p call to be used
on it.
Because as a Roblox developer, you need to be aware that there are certain functions in the Roblox
API that may produce an error.
Typically, these functions end with the word async, meaning asynchronous.
Those functions make some kind of web requests to the Roblox servers and due to issues outside of your
control, maybe there's some kind of internet issue or something.
It might return some kind of HTTP error, and we need to be able to handle that using HTTP call.
So let's go search through the documentation and let's go to our random service.
Like let's go to the player service right.
We'll go to the player service.
And let's say we want to get the thumbnail picture of a player's head right.
So let's see if there's a function in here somewhere for that.
Ah yes here it is.
Get user thumbnail and look at that.
It says async and this function yields.
So it says this function returns the content URL of an image of a player's avatar given their user id.
So this function makes a web request and web requests can possibly fail.
So this is an example of a function that we need to wrap an API call.
So let's practice doing that.
Let's grab the player service game, get service players.
And every time a player joins our game.
So we'll listen to the player added event.
We'll connect a Lambda function to that.
And this function is going to be passed the player that has joined our game.
And we want to get the image ID of the thumbnail for this player's head so we can use the player service
function.
Get user thumbnail async I need to give it the user id of the player, so we can just do player dot
user id.
We need to give it a thumbnail type.
So enum dot thumbnail type dot.
Let's do a headshot.
And what else do we need to pass to it.
We need to pass the thumbnail size so we can do enum dot thumbnail size.
And let's just do 180 by 180 pixels.
And then let's go ahead and print out this image ID into the console.
Now, it isn't likely that I'm going to run into an error because I'm not experiencing any network issues,
but this is a function we should wrap in a p call.
Otherwise if it errors, we're going to have problems in our code.
So what we should do instead is we should call p Pcall, and we're going to pass a lambda function to
this.
And we're going to return the result of this function right here.
So let's cut that and then let's paste that here.
And we're going to return it.
So this should return to us success.
And then the result.
And then if we were not successful then we can simply spit out a warning, which is going to be the
error message.
Otherwise, if we were successful, we can go ahead and, you know, print out our image ID, which
is going to be equal to the results.
I mean, technically I don't need this middleman variable.
I could just print out results directly.
And now let's go ahead and play test the game and see what happens.
There we go.
I got my IRL RGB thumbnail for my particular user ID and the width and the height.
Pretty cool.
And we can use this in like image labels and stuff to display, uh, the headshot of my character.
But as you can see, no network issue.
What I'm going to do is I'm going to artificially insert an error in here.
We're just going to say some network error happened.
And then let's go ahead and playtest the game.
There we go.
Some network error happened, but we did not halt the execution of the code.
We did not have an error message appeared.
We simply printed a warning into the console.
And if you had any other code down here that still needed to execute, no matter you successfully got
the user thumbnail or not, we are able to execute that code and we're not going to have any issues.
There are a lot more functions in the API that we need to use pickle on.
For example, get user id from name async.
This is another asynchronous function that we should wrap an API call.
Let's keep looking.
Get name from user id async.
Here's another function we need to wrap in API call.
Get humanoid description from user id.
This one doesn't have async in it, but this is a function that does yield.
It's asynchronous, and because it does make a web request, we should also wrap this one in AP call.
Same thing with this one.
Get humanoid description from outfit id it also yields because it makes a web request.
So we should wrap it AP call.
Same thing with this one.
Get friends async.
Another one we should wrap in AP call.
So there's actually a lot in the player service that we should be wrapping in a p call.
Get character appearance info asynchronous.
Another one we need to wrap in a p call.
Let's see.
Create humanoid model from user id this one also yield.
So I assume that we should also use p call on this just in case because it does make a web request.
Create humanoid model from description.
Another one we should use p call on ban async.
Obviously we should use p call on this one.
By the way, this is a new function that you can have banned players, which is pretty cool.
Team chat.
That is a plugin property or something.
So we don't need to worry about any of these ones.
Get players.
Obviously this is not a function you need to use p call on.
It doesn't yield.
That doesn't make any web requests.
No need for P call on that one.
And then we have some other functions we don't need to use p call on okay.
Pretty cool.
Let's take a look at another service.
Alrighty.
Here's another service that has a lot of methods or functions inside of it.
We should be wrapping in P call and that's the marketplace service.
Let's just go ahead and look through here and see if we can immediately find some functions.
We should use P call on this one.
We shouldn't.
These are just prompting UI for players.
There's no yielding and there is no network requests being made here.
We don't have to handle any errors with these functions.
But let's keep an eye out for any functions that are tagged as yielding, or if they have the word async
in the end of their name.
So let's keep looking.
Oh, here we go.
Here's one right here.
Get developer products.
Async returns a pages object which contains information for all of the current experiences developer
products.
This function yields.
It's an asynchronous function and makes a web request.
This is a function we should wrap in p call.
Here's another one.
Get product info.
This one doesn't have the word async in its name, but it does yield.
And it does make a web request to get information about a particular product in the Roblox catalog or
something like that.
So this is another function we should use Pcall on.
And by this point, you should have a pretty good idea of what functions in the API you should be catching
errors with.
Like.
Here's another one you need to watch out for errors with this function.
You need to watch out for errors with this function.
So there's a whole bunch of different ones in here.
It's pretty easy to spot if it has async in the name.
Use P call.
If it yields.
You'll likely need to use p call if any of the definitions of the function talk about making web requests
or anything, you should probably use p call.
So that is all from me for this lecture.
Yes, this lecture was likely a bit boring, but as a Roblox developer, it is super important for you
to understand how to debug errors and how to handle errors using Pcall.
This is one aspect of the development world that you cannot skip and you cannot bypass.
You need to learn this stuff otherwise.
Thank you for watching and I will see you in the next lecture.